#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/generate.h>
#include <thrust/functional.h>
#include <thrust/random.h>
#include <thrust/transform.h>
#include <thrust/sort.h>
#include <thrust/reduce.h>
#include <thrust/iterator/constant_iterator.h>
#include <thrust/scatter.h>

#include "util.h"

void host_simulation(const int memory_length,
                     const int num_weights,
                     const int num_creatures,
                     const int num_rounds_per_match,
                     thrust::host_vector<unsigned int> &h_random,
                     const thrust::host_vector<float> &h_creature_weights,
                     thrust::host_vector<int> &h_creature_scores,
                     int R,
                     int S,
                     int T,
                     int P,
                     const bool use_const,
                     const bool use_random,
                     const bool use_same);

void device_simulation(const int memory_length,
                       const int num_weights,
                       const int num_creatures,
                       const int num_rounds_per_match,
                       thrust::device_vector<unsigned int> &d_random,
                       const thrust::device_vector<float> &d_creature_weights,
                       thrust::device_vector<int> &d_creature_scores,
                       int R,
                       int S,
                       int T,
                       int P,
                       const bool use_const,
                       const bool use_random,
                       const bool use_same);

void normalize_all_weights(const int num_creatures,
                           const int num_weights,
                           thrust::device_vector<float> &d_creature_weights,
                           thrust::host_vector<float> &h_creature_weights);

// Memory funtions
__host__ __device__
inline float mem_to_a(int mem)
{
  if(mem == 0) return 0;
  return (mem < 3) ? 1 : -1;
}

__host__ __device__ 
inline float mem_to_b(int mem)
{
  if(mem == 0) return 0;
  return (mem & 1) ? 1 : -1;
}

__host__ __device__
inline int ab_to_mem(float da, float db)
{
  return (da>0.0f ? 0 : 2) + (db>0.0f ? 1 : 2);
}

// Upper triangular matrix functions
__host__ __device__
inline unsigned int utlm_num_elements(unsigned int n)
{
  return (n - 1) * n / 2;
}

__host__ __device__
inline unsigned int utlm_index_to_row(unsigned int i, unsigned int n)
{
  float tnp1 = (float)n - 0.5f;
  return (unsigned int)(tnp1 - sqrt(tnp1 * tnp1 - 2*(float)i - 0.1f));
}


__host__ __device__
inline unsigned int utlm_index_to_col(unsigned int i,
                                      unsigned int r,
                                      unsigned int n)
{
  return 1 + i - (n-1)*r + (r + 1)*r/2;
}

// Memory bit field manager
__host__ __device__
inline void reset_memory(unsigned int &memory)
{
  memory = 0;
}

__host__ __device__
inline unsigned int get_memory(unsigned int memory, unsigned int ago)
{
  return (memory >> (ago * 3)) & 7; //== 111b
}

__host__ __device__
inline void push_memory(unsigned int &memory, unsigned int val)
{
  memory <<= 3;
  memory |= val;
}

// returns true if two input values x and y are equal after integer division by num
struct equal_divide_n : thrust::binary_function<unsigned int, unsigned int, bool>
{
  equal_divide_n(const unsigned int n)
    : num(n)
  {}

  __host__ __device__
  bool operator()(const unsigned int x, const unsigned int y) const
  {
    return x/num == y/num;
  }

  unsigned int num;
};
